/**
 * @Project TMHSC
 * @Author Tran Manh Hung
 * @Email: hungtmit@gmail.com | tmh@xwebgate.com
 * @Copyright © 2011 TMH. All rights reserved
 * @Createdate Feb 1, 2011
 * @Createtime 1:48:57 AM
 */

package com.tmh.study.aASA
{

	public class SteeredVehicle extends Vehicle
	{
		private var _maxForce:Number = 1;

		private var _arrivalThreshold:Number = 100;

		private var _wanderAngle:Number = 0;

		private var _wanderDistance:Number = 10;

		private var _wanderRadius:Number = 5;

		private var _wanderRange:Number = 1;

		private var _avoidDistance:Number = 200;

		private var _avoidBuffer:Number = 20;

		private var _pathIndex:int = 0;

		private var _pathThreshold:Number = 20;

		private var _inSightDist:Number = 200;

		private var _tooCloseDist:Number = 50;

		private var _steeringForce:Vector2D;

		public function SteeredVehicle()
		{
			_steeringForce = new Vector2D();
			super();
		}

		override public function update():void
		{
			_steeringForce.truncate(_maxForce);
			_steeringForce = _steeringForce.devide(_mass);
			_velocity = _velocity.add(_steeringForce);
			_steeringForce = new Vector2D();
			super.update();
		}

		public function seek(target:Vector2D):void
		{
			var desiredVelocity:Vector2D = target.subtract(_position);
			desiredVelocity.normalize();
			desiredVelocity = desiredVelocity.multiply(_maxSpeed);

			var force:Vector2D = desiredVelocity.subtract(_velocity);

			_steeringForce = _steeringForce.add(force);
		}

		public function flee(target:Vector2D):void
		{
			var desiredVelocity:Vector2D = target.subtract(_position);
			desiredVelocity.normalize();
			desiredVelocity = desiredVelocity.multiply(_maxSpeed);

			var force:Vector2D = desiredVelocity.subtract(_velocity);

			_steeringForce = _steeringForce.subtract(force);
		}

		public function arrive(target:Vector2D):void
		{
			var desiredVelocity:Vector2D = target.subtract(_position);
			desiredVelocity.normalize();

			var dist:Number = _position.dist(target);

			if (dist > _arrivalThreshold)
			{
				desiredVelocity = desiredVelocity.multiply(_maxSpeed);
			}
			else
			{
				desiredVelocity = desiredVelocity.multiply(_maxSpeed * dist / _arrivalThreshold);
			}

			var force:Vector2D = desiredVelocity.subtract(_velocity);

			_steeringForce = _steeringForce.add(force);
		}

		public function pursue(target:Vehicle):void
		{
			var lookAheadTime:Number = position.dist(target.position) / _maxSpeed;
			var predictedTarget:Vector2D = target.position.add(target.velocity.multiply(lookAheadTime));

			seek(predictedTarget);
		}

		public function evade(target:Vehicle):void
		{
			var lookAheadTime:Number = position.dist(target.position) / _maxSpeed;
			var predictedTarget:Vector2D = target.position.add(target.velocity.multiply(lookAheadTime));

			flee(predictedTarget);
		}

		public function wander():void
		{
			var center:Vector2D = _velocity.clone().normalize().multiply(_wanderDistance);

			var offset:Vector2D = new Vector2D();
			offset.length = _wanderRadius;
			offset.angle = _wanderAngle;
			_wanderAngle += Math.random() * _wanderRange - _wanderRange * .5;

			var force:Vector2D = center.add(offset);

			_steeringForce = _steeringForce.add(force);
		}

		public function avoid(circles:Array):void
		{
			for (var i:int = 0; i < circles.length; i++)
			{
				var circle:Circle = circles[i] as Circle;
				var heading:Vector2D = _velocity.clone().normalize();

				// vector between circle and vehicle
				var difference:Vector2D = circle.position.subtract(_position);
				var dotProd:Number = difference.dotProd(heading);

				if (dotProd > 0)
				{
					var feeler:Vector2D = heading.multiply(_avoidDistance);
					var projection:Vector2D = heading.multiply(dotProd);
					var dist:Number = projection.subtract(difference).length;

					if (dist < circle.radius + _avoidBuffer && projection.length < feeler.length)
					{
						var force:Vector2D = heading.multiply(_maxSpeed);
						force.angle += difference.sign(_velocity) * Math.PI / 2;
						force = force.multiply(1 - projection.length / feeler.length);

						_steeringForce = _steeringForce.add(force);

						_velocity = _velocity.multiply(projection.length / feeler.length);
					}
				}
			}
		}

		public function followPath(path:Array, loop:Boolean = false):void
		{
			var wayPoint:Vector2D = path[_pathIndex];

			if (wayPoint == null)
			{
				return;
			}

			if (_position.dist(wayPoint) < _pathThreshold)
			{
				if (_pathIndex >= path.length - 1)
				{
					if (loop)
					{
						_pathIndex = 0;
					}
				}
				else
				{
					_pathIndex++;
				}
			}

			if (_pathIndex >= path.length - 1 && !loop)
			{
				arrive(wayPoint);
			}
			else
			{
				seek(wayPoint);
			}
		}

		/**
		 * ??? Tất cả vehicle dường như tập trung vào góc trên trái...
		 */
		public function flock(vehicles:Array):void
		{
			var avarageVelocity:Vector2D = new Vector2D();
			var avaragePosition:Vector2D = new Vector2D();
			var inSightCount:int = 0;

			for (var i:int = 0; i < vehicles.length; i++)
			{
				var vehicle:Vehicle = vehicles[i] as Vehicle;

				if (vehicle != this && inSight(vehicle))
				{
					avarageVelocity = avarageVelocity.add(vehicle.velocity);
					avaragePosition = avaragePosition.add(vehicle.position);

					if (tooClose(vehicle))
					{
						evade(vehicle);
					}

					inSightCount++;
				}

				if (inSightCount > 0)
				{
					avarageVelocity = avarageVelocity.devide(inSightCount);
					avaragePosition = avaragePosition.devide(inSightCount);

					seek(avaragePosition);

					_steeringForce.add(avarageVelocity.subtract(_velocity));
				}
			}
		}

		public function inSight(vehicle:Vehicle):Boolean
		{
			if (_position.dist(vehicle.position) > _inSightDist)
			{
				return false;
			}

			var heading:Vector2D = _velocity.clone().normalize();
			var difference:Vector2D = vehicle.position.subtract(_position);
			var dotProd:Number = difference.dotProd(heading);

			if (dotProd < 0)
			{
				return false;
			}

			return true;
		}

		public function tooClose(vehicle:Vehicle):Boolean
		{
			return (_position.dist(vehicle.position) < _tooCloseDist);
		}

		public function get maxForce():Number
		{
			return _maxForce;
		}

		public function set maxForce(value:Number):void
		{
			_maxForce = value;
		}

		public function get arrivalThreshold():Number
		{
			return _arrivalThreshold;
		}

		public function set arrivalThreshold(value:Number):void
		{
			_arrivalThreshold = value;
		}

		public function get wanderAngle():Number
		{
			return _wanderAngle;
		}

		public function set wanderAngle(value:Number):void
		{
			_wanderAngle = value;
		}

		public function get wanderDistance():Number
		{
			return _wanderDistance;
		}

		public function set wanderDistance(value:Number):void
		{
			_wanderDistance = value;
		}

		public function get wanderRadius():Number
		{
			return _wanderRadius;
		}

		public function set wanderRadius(value:Number):void
		{
			_wanderRadius = value;
		}

		public function get wanderRange():Number
		{
			return _wanderRange;
		}

		public function set wanderRange(value:Number):void
		{
			_wanderRange = value;
		}

		public function get avoidDistance():Number
		{
			return _avoidDistance;
		}

		public function set avoidDistance(value:Number):void
		{
			_avoidDistance = value;
		}

		public function get avoidBuffer():Number
		{
			return _avoidBuffer;
		}

		public function set avoidBuffer(value:Number):void
		{
			_avoidBuffer = value;
		}

		public function get pathIndex():int
		{
			return _pathIndex;
		}

		public function set pathIndex(value:int):void
		{
			_pathIndex = value;
		}

		public function get pathThreshold():Number
		{
			return _pathThreshold;
		}

		public function set pathThreshold(value:Number):void
		{
			_pathThreshold = value;
		}

		public function get inSightDist():Number
		{
			return _inSightDist;
		}

		public function set inSightDist(value:Number):void
		{
			_inSightDist = value;
		}

		public function get tooCloseDist():Number
		{
			return _tooCloseDist;
		}

		public function set tooCloseDist(value:Number):void
		{
			_tooCloseDist = value;
		}
	}
}